﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using ESRI.ArcGIS.Geodatabase;
using Lwllm.ArcengineProxy.Common;
using Lwllm.ArcengineProxy.Core.Interfaces;
using Lwllm.ArcengineProxy.Core.Models;
using Lwllm.ArcengineProxy.Core.Proxys;

namespace Lwllm.ArcengineProxy.Core.Implements
{
    public class ApQueryable<TEntity> : FeatureClassProxy, IApQueryable<TEntity> where TEntity : BaseEntity
    {
        private readonly Queue<Expression> _expressions = new Queue<Expression>();
        private int _skipCount = 0;
        private int _takeCount = -1;

        public ApQueryable(IFeatureClass featureClass) : base(featureClass)
        {
        }

        private TEntity CreateEntity(IFeature feature)
        {
            return (TEntity)Activator.CreateInstance(typeof(TEntity), feature, this);
        }

        public TEntity FirstOrDefault()
        {
            var cursor = FeatrueClass.Search(null, false);
            var feature = cursor.NextFeature();
            cursor.ComDispose();
            //var entity = (TEntity)Activator.CreateInstance(typeof(TEntity), feature, this);
            var entity = CreateEntity(feature);
            entity.GenerateMembers();
            return entity;
        }

        public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            throw new NotImplementedException();
        }

        public TEntity GetByFid(int fid)
        {
            var feature = this.GetFeature(fid);
            var entity = (TEntity)Activator.CreateInstance(typeof(TEntity), feature, this);
            entity.GenerateMembers();
            return entity;
        }

        public IApQueryable<TEntity> GetByFids(IEnumerable<int> fids)
        {
            throw new NotImplementedException();
        }

        public IApQueryable<TEntity> Where(Expression<Func<TEntity, bool>> predicate)
        {
            _expressions.Enqueue(predicate.Body);
            return this;
        }

        public IEnumerable<TEntity> ToList()
        {
            var whereClause = "(1=1) ";
            while (_expressions.Count > 0)
            {
                var expression = _expressions.Dequeue();
                if (expression is BinaryExpression)
                {
                    var binaryResolve = new BinaryResolve<TEntity>(expression);
                    whereClause += $"and {binaryResolve.Build()}";
                }
            }
            IQueryFilter filter = new QueryFilterClass();
            filter.WhereClause = whereClause;
            IFeatureCursor featureCursor = this.FeatrueClass.Search(filter, false);
            var index = 0;
            if (_skipCount > 0)
            {
                while (index < _skipCount)
                {
                    featureCursor.NextFeature();
                    index++;
                }
            }

            if (_takeCount >= 0)
            {
                var count = 0;
                IFeature feature = featureCursor.NextFeature();
                while (count < _takeCount && feature != null)
                {
                    //var entity = (TEntity)Activator.CreateInstance(typeof(TEntity), feature, this);
                    var entity = CreateEntity(feature);
                    yield return entity;
                    count++;
                    feature = featureCursor.NextFeature();
                }
            }
            else
            {
                IFeature feature = featureCursor.NextFeature();
                while (feature != null)
                {
                    var entity = (TEntity)Activator.CreateInstance(typeof(TEntity), feature, this);
                    yield return entity;
                    feature = featureCursor.NextFeature();
                }
            }
            featureCursor.ComDispose();
            filter.ComDispose();
        }

        public IApQueryable<TEntity> Skip(int count)
        {
            _skipCount = count;
            return this;
        }

        public IApQueryable<TEntity> Take(int count)
        {
            _takeCount = count;
            return this;
        }
    }
}